/* MQTT (over TCP) Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"

#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"

#include "esp_log.h"
#include "mqtt_client.h"

#include "pan_main.h"
#include "cJSON.h"
static const char *TAG = "PAN_MQTT";
// ElderMan
// #define MQTT_Username "device"
// #define MQTT_Password "sH9xBWo0wS4Xzy%nDlCH"
// Okin
#define MQTT_Username "device"
#define MQTT_Password "HSQz4QY1tWzwWFKM"
uint8_t MqttConnectFlag;
esp_mqtt_client_handle_t user_client;

extern xQueueHandle start_OTA_queue;
extern char config_topic[80];
extern char lastwill_topic[80];
extern char firmware_topic[80];
extern char subscribe_topic[80];
// extern char press_topic[80];
extern char macaddress[6];
unsigned char TimeStampBuffer[17] = {0};
static uint64_t local_timestamp = 0;
int year = 0, mouth = 0, day = 0;

extern APP_INFO app_info;
extern APP_INFO ver_info;

unsigned int My_crc32(unsigned char *ptr, int len);

static void log_error_if_nonzero(const char *message, int error_code)
{
    if (error_code != 0)
    {
        ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
    }
}

/*
 * @brief Event handler registered to receive MQTT events
 *
 *  This function is called by the MQTT client event loop.
 *
 * @param handler_args user data registered to the event.
 * @param base Event base for the handler(always MQTT Base in this example).
 * @param event_id The id for the received event.
 * @param event_data The data for the event, esp_mqtt_event_handle_t.
 */
static char mqtt_connect_error_count = 0;
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    cJSON *json_Data = NULL;
    cJSON *json_OPM_CalTimes = NULL;
    cJSON *json_PConfig = NULL;
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
    // 获取MQTT客户端结构体指针
    esp_mqtt_event_handle_t event = event_data;
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    char SendData[80] = {0};
    // 通过事件ID来分别处理对应的事件
    switch ((esp_mqtt_event_id_t)event_id)
    {
        // 建立连接成功
    case MQTT_EVENT_CONNECTED: // MQTT 连接上了
        mqtt_connect_error_count = 0;
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
        MqttConnectFlag = 1;
        msg_id = esp_mqtt_client_subscribe(client, (const char *)&subscribe_topic, 1); // 订阅主题
        sendData("\xA5\x5A\x02\x00\x0A\x01", 6);                                       // MQTT连上
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);

        sprintf(SendData, "{\"connected\":1,\"version\":\"%d.%d.%d\",\"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\"}",
                ver_info.version[0], ver_info.version[1], ver_info.version[2],
                macaddress[0], macaddress[1], macaddress[2], macaddress[3], macaddress[4], macaddress[5]);
        msg_id = esp_mqtt_client_publish(user_client, (const char *)&lastwill_topic, (const char *)SendData, strlen(SendData), 0, 0);
        vTaskDelay(500);
        msg_id = esp_mqtt_client_publish(user_client, (const char *)&config_topic, (const char *)"{\"ASK\":1}", 9, 0, 0);
        break;
        // 客户端断开连接
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        esp_mqtt_client_reconnect(client); // 重连服务器
        vTaskDelay(2000 / portTICK_PERIOD_MS);
        MqttConnectFlag = 0;
        mqtt_connect_error_count++;
        ESP_LOGI(TAG, " mqtt_connect_error_count is %d", mqtt_connect_error_count);
        if (mqtt_connect_error_count > 20)
        {
            esp_restart();
        }
        sendData("\xA5\x5A\x02\x00\x0A\x00", 6); // MQTT断开
        break;
        // 主题订阅成功
    case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
        // msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
        // ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
        break;
        // 取消订阅
    case MQTT_EVENT_UNSUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
        break;
        //  主题发布成功
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
        break;
        // 已收到订阅的主题消息
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "MQTT_EVENT_DATA");
        printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
        printf("DATA=%.*s\r\n", event->data_len, event->data);

        if (strstr(event->topic, "/ota") != NULL && event->data_len > 0)
        {
            xQueueSend(start_OTA_queue, event->data, portMAX_DELAY);
        }
        else if (strstr(event->topic, "/calibration") != NULL && event->data_len > 0)
        { /* 解析整段JSO数据 */
            json_Data = cJSON_Parse(event->data);
            if (json_Data == NULL)
            {
                printf("parse fail.\n");                // 解析失败则直接转发
                sendData(event->data, event->data_len); // 转发给控制盒
                return;
            } /* 依次根据名称提取JSON数据（键值对） */
            json_OPM_CalTimes = cJSON_GetObjectItem(json_Data, "time");
            if (json_OPM_CalTimes != NULL)
            {
                uint64_t s_timestamp = 0;
                uint32_t s_timestamp_crc32 = 0;
                TimeStampBuffer[0] = 0xA5;
                TimeStampBuffer[1] = 0x5A;
                TimeStampBuffer[2] = 0x0D;
                TimeStampBuffer[3] = 0x00;
                TimeStampBuffer[4] = 0x20;
                s_timestamp = atoll(json_OPM_CalTimes->valuestring);
                local_timestamp = s_timestamp;
                memcpy(TimeStampBuffer + 5, &s_timestamp, sizeof(s_timestamp));
                s_timestamp_crc32 = My_crc32(TimeStampBuffer, sizeof(TimeStampBuffer) - 4);
                memcpy(TimeStampBuffer + 13, &s_timestamp_crc32, 4);
                printf("time: %lld Hex:%llx \n", s_timestamp, s_timestamp);
                // 串口发送三次防止无应答
                for (char i = 0; i < 3; i++)
                {
                    sendData((const char *)TimeStampBuffer, sizeof(TimeStampBuffer));
                    vTaskDelay(10);
                }
                // 释放内存
                cJSON_Delete(json_Data);
                break;
            }
        }
        else if (strstr(event->topic, "/config") != NULL && event->data_len > 0)
        {
            char *str = NULL;
            char config_sex = 0;
            char config_height = 0;
            char config_weight = 0;
            char config_birthday[16] = {0};
            char DConfig_array_size = 0;
            char SendConfigFrameBuffer[28] = {0};
            cJSON *json_item = NULL;
            /* 解析整段JSO数据 */
            json_Data = cJSON_Parse(event->data);
            if (json_Data == NULL)
            {
                ESP_LOGE(TAG, "parse fail.\n");         // 解析失败则直接转发
                sendData(event->data, event->data_len); // 转发给控制盒
                return;
            }
            else
            {
                str = cJSON_Print(json_Data);
                ESP_LOGD(TAG, "json_Data str is %s", str);
            }
            json_PConfig = cJSON_GetObjectItem(json_Data, "Pconfig");
            if (json_PConfig != NULL)
            {
                char *token = NULL;
                uint8_t count = 0;
                json_item = cJSON_GetObjectItem(json_PConfig, "sex");
                config_sex = json_item->valueint;
                json_item = cJSON_GetObjectItem(json_PConfig, "height");
                config_height = json_item->valueint;
                json_item = cJSON_GetObjectItem(json_PConfig, "weight");
                config_weight = json_item->valueint;
                json_item = cJSON_GetObjectItem(json_PConfig, "birthday");
                memcpy(config_birthday, json_item->valuestring, strlen(json_item->valuestring));
                /* 获取第一个子字符串 */
                token = strtok(config_birthday, "-");
                /* 继续获取其他的子字符串 */
                while (token != NULL)
                {
                    count++;
                    switch (count)
                    {
                    case 1:
                        year = atoi(token);
                        printf("year: %d\n", year);
                        break;
                    case 2:
                        mouth = atoi(token);
                        printf("mouth: %d\n", mouth);
                        break;
                    case 3:
                        day = atoi(token);
                        printf("day: %d\n", day);
                        break;
                    default:
                        break;
                    }
                    token = strtok(NULL, "-");
                }
                ESP_LOGI(TAG, "sex: %d  height: %d weight: %d  birthday %s\r\n", config_sex, config_height, config_weight, config_birthday);
            }
        }
        else
            sendData(event->data, event->data_len); // 转发给控制盒
        break;
        // 客户端遇到错误
    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT)
        {
            log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
            log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
            log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
            ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
        }
        MqttConnectFlag = 0;
        sendData("\xA5\x5A\x02\x00\x0A\x00", 6); // MQTT断开
        esp_restart();
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    }
}

void mqtt_app_start(void)
{
    // 定义一个mqtt客户端配置结构体，输入MQTT的url
    esp_mqtt_client_config_t mqtt_cfg = {
        .host = &app_info.server_ip,
        .port = app_info.port,
        //   .client_id = macaddress,
        .username = MQTT_Username,
        .password = MQTT_Password,
        .keepalive = 10,
        .transport = MQTT_TRANSPORT_OVER_TCP,
        .lwt_topic = lastwill_topic,
        .lwt_qos = 1,
        .lwt_msg = "{\"connected\":0}",
        .lwt_msg_len = 15,
    };
#if CONFIG_BROKER_URL_FROM_STDIN
    char line[128];

    if (strcmp(mqtt_cfg.uri, "FROM_STDIN") == 0)
    {
        int count = 0;
        printf("Please enter url of mqtt broker\n");
        while (count < 128)
        {
            int c = fgetc(stdin);
            if (c == '\n')
            {
                line[count] = '\0';
                break;
            }
            else if (c > 0 && c < 127)
            {
                line[count] = c;
                ++count;
            }
            vTaskDelay(10 / portTICK_PERIOD_MS);
        }
        mqtt_cfg.uri = line;
        printf("Broker url: %s\n", line);
    }
    else
    {
        ESP_LOGE(TAG, "Configuration mismatch: wrong broker url");
        abort();
    }
#endif /* CONFIG_BROKER_URL_FROM_STDIN */
    // 2、通过esp_mqtt_client_init获取一个MQTT客户端结构体指针，参数是MQTT客户端配置结构体
    user_client = esp_mqtt_client_init(&mqtt_cfg);
    /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
    // 3、注册MQTT事件
    esp_mqtt_client_register_event(user_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
    // 4、开启MQTT功能
    esp_mqtt_client_start(user_client);
}

void SendToServerOTAbegin()
{
    char OTA_Info[10] = {0};
    sprintf(OTA_Info, "{\"ota\":0}"); // 打印一个0
    esp_mqtt_client_publish(user_client, (const char *)&firmware_topic, (const char *)OTA_Info, 9, 0, 0);
}
void SendToServerOTAover()
{
    char OTA_Info[10] = {0};
    sprintf(OTA_Info, "{\"ota\":1}"); // 打印1个1
    esp_mqtt_client_publish(user_client, (const char *)&firmware_topic, (const char *)OTA_Info, 9, 0, 0);
}

// 直接计算法计算crc
unsigned int My_crc32(unsigned char *ptr, int len)
{
    unsigned int i;
    unsigned int crc = 0xFFFFFFFF;

    while (len--)
    {
        crc ^= (unsigned int)(*ptr++) << 24;
        for (i = 0; i < 8; ++i)
        {
            if (crc & 0x80000000)
                crc = (crc << 1) ^ 0x04C11DB7;
            else
                crc <<= 1;
        }
    }
    return crc;
}